package anonymouschat.messages;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.IvParameterSpec;

import anonymouschat.messages.clientrequests.UserLoginRequest;
import anonymouschat.security.SecurityUtils;
import anonymouschat.utils.Constants;
import anonymouschat.utils.Print;

public class EncryptedMessageWrapperMessage extends Message implements
		Serializable {

	private static final long serialVersionUID = -5438981469645327144L;

	private String nextHop;

	private byte[] encryptedMessage;

	private boolean isAsymmetricEnc; // Is encrypted Asymetrically (pub/priv) or
	// with Symmetric Key

	private byte[] iv;

	private Long connectionNumber = null;

	private UserLoginRequest login = null;

	private int direction = Constants.UPSTREAM;

	private boolean usePersistentRoute = true;

	/**
	 * Wraps an object in encryption
	 * 
	 * @param enc
	 *            a cipher engine
	 * @param messageToWrap
	 *            a user message
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 * @throws IOException
	 */
	public EncryptedMessageWrapperMessage(Cipher enc,
			Serializable messageToWrap, boolean isAsymmetricEnc)
			throws IllegalBlockSizeException, BadPaddingException, IOException {

		Print.debug("Adding a layer of encryption to: "
				+ messageToWrap.toString());
		this.isAsymmetricEnc = isAsymmetricEnc;

		byte[] unencryptedByteArray = SecurityUtils
				.objectToByteArray(messageToWrap);
		if (isAsymmetricEnc) {
			// If the messageToWrap is larger than bytes when serialized
			// Break it up into parts MAX_ENC_SEGMENT_BYTES bytes long, encrypt
			// with RSA & concatenate the encrypted chunks
			// This will be detected and reversed on the other side.
			ByteArrayOutputStream bos = new ByteArrayOutputStream();

			int offset = 0;
			int maxEncLength = Constants.MAX_ENC_SEGMENT_BYTES;
			while (offset < unencryptedByteArray.length) {

				if (unencryptedByteArray.length - offset < maxEncLength) {
					byte[] outputBytes = new byte[unencryptedByteArray.length
							- offset];
					System.arraycopy(unencryptedByteArray, offset, outputBytes,
							0, unencryptedByteArray.length - offset);
					bos.write(enc.doFinal(outputBytes));
					break;
				}

				byte[] outputBytes = new byte[maxEncLength];
				System.arraycopy(unencryptedByteArray, offset, outputBytes, 0,
						maxEncLength);
				bos.write(enc.doFinal(outputBytes));
				offset += maxEncLength;
			}
			this.encryptedMessage = bos.toByteArray();

		} else {
			this.encryptedMessage = enc.doFinal(unencryptedByteArray);
			this.iv = enc.getIV();
		}
	}

	public Long getConnectionNumber() {
		return connectionNumber;
	}

	public void setConnectionNumber(long connectionNumber) {
		Print.debug("Setting connection number: " + connectionNumber);
		this.connectionNumber = connectionNumber;
	}

	public boolean isAsymmetricEncrypted() {
		return isAsymmetricEnc;
	}

	public IvParameterSpec getIV() {
		return new IvParameterSpec(iv);
	}

	public void setNextHop(String dest) {
		nextHop = dest;
	}

	/**
	 * Returns the next hop in the anonymized routing chain
	 * 
	 * @return
	 */
	public String getNextHop() {
		return nextHop;
	}

	public byte[] getencryptedMessage() {
		return encryptedMessage;
	}

	public void setUserLogin(UserLoginRequest login) {
		this.login = login;
	}

	public UserLoginRequest getUserLogin() {
		return login;
	}

	public boolean isGoingUpstream() {
		return (this.direction == Constants.UPSTREAM);
	}

	public void setDirection(int direction) {
		this.direction = direction;
	}

	public int getDirection() {
		return this.direction;
	}

	private Long incomingId = null;
	public boolean goingUp = true;

	public void setDownId(Long downId) {
		incomingId = downId;
	}

	public Long getIncomingId() {
		return incomingId;
	}

	public boolean usePersistentRoute() {
		return usePersistentRoute;
	}

	public void setUsePersistentRoute(boolean shouldCreate) {
		usePersistentRoute = shouldCreate;
	}

	@Override
	public String getAllContents() {
		String ret = "encryptedMessageWrapperMessage:\n\t";
		ret += "nextHop: " + super.fixNull(this.nextHop)
				+ "\n\tisAsymmetricEnc: " + fixNull(this.isAsymmetricEnc)
				+ "\n\tconnNum: " + fixNull(this.connectionNumber)
				+ "\n\tdirection: " + fixNull(this.direction);

		return ret;

	}

}
